package org.wattdepot.gdata.kukuicup.updater;

import it.sauronsoftware.cron4j.Scheduler;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.gdata.kukuicup.service.GDataService;
import org.wattdepot.gdata.kukuicup.service.GDataView;
import org.wattdepot.gdata.kukuicup.service.WattDepotService;


/**
 * The superclass for all updaters, which provides common operations and specifies the 
 * methods that must be implemented (cancel(), run()).
 * @author Philip Johnson
 */
public abstract class Updater {
  
  /** The name of this updater. */
  protected String name;
  
  /** Supports connecting to a WattDepot server and getting KukuiCup data. */
  protected WattDepotService wattDepotService;
  
  /** Supports connecting to a Google Docs spreadsheet and writing data to it. */
  protected GDataService gdataService; 
  
  /** The list of WattDepot sources to be queried by this updater. */
  protected List<String> sources = new ArrayList<String>();
  
  /** Holds any timers used by updaters. */
  protected List<Timer> timers = new ArrayList<Timer>();

  /** The Cron4J schedulers used by any updaters. */
  protected List<Scheduler> schedulers = new ArrayList<Scheduler>();

  /** The URL corresponding to the spreadsheet name. */ 
  protected URL spreadsheetURL;

  /** A view of this spreadsheet. */
  protected GDataView spreadsheetView;

  /** The list of WattDepot source names associated with this spreadsheet. */
  protected List<String> sourceNames;
  
  
  /**
   * Creates this Updater, initializing its name and the spreadsheet it operates on.
   * Also reads in the spreadsheet to determine the names of sources to be analyzed.
   * All subclasses should define a constructor that invokes this one.
   * @param name The name of this updater, as well as the name of the spreadsheet.
   * @param wattDepotService The WattDepot service. 
   * @param gdataService The GData service. 
   * @throws Exception If problems occur.
   */
  public Updater(String name, WattDepotService wattDepotService, GDataService gdataService) 
  throws Exception {
    this.name = name;
    this.wattDepotService = wattDepotService;
    this.gdataService = gdataService;
    this.spreadsheetURL = gdataService.getSpreadsheetURL(name);
    this.spreadsheetView = new GDataView(gdataService, spreadsheetURL);
    // Now find the source names, which must be the contents of column 1 of the spreadsheet except
    // for the very first one, which is the column header. 
    List<String> columnOne = spreadsheetView.getCellData(spreadsheetView.getColumn(1));
    this.sourceNames = columnOne.subList(1, columnOne.size());
    System.out.format("Sources for %s are %s%n", this.name, this.sourceNames);
    // Throw an exception and terminate this program if we don't recognize all of the sources. 
    for (String sourceName : sourceNames) {
      try {
        this.wattDepotService.getClient().getSource(sourceName);
      }
      catch (Exception e) {
        String msg = String.format(
            "Error in spreadsheet %s: Unknown source %s for WattDepot server %s ", 
            name, sourceName, this.wattDepotService.getClient().getWattDepotUri());
        System.out.println(msg);
        throw new RuntimeException(msg, e);
      }
    }
  }
  
  /** Terminates any threads initiated by this updater. */
  public void cancel() {
    if (!timers.isEmpty()) {
      System.out.println("Cancelling timers for: " + name);
      for (Timer timer : timers) {
        timer.cancel();
      }
    }
    if (!schedulers.isEmpty()) {
      System.out.println("Cancelling schedulers for: " + name);
      for (Scheduler scheduler : schedulers) {
        scheduler.stop();
      }
    }
  }
  
  /**
   * Return true if the WattDepot source is virtual.
   * @param sourceName The source.
   * @return True if virtual.
   */
  protected boolean isVirtual(String sourceName) {
    WattDepotClient client = wattDepotService.getClient();
    try {
      return client.getSource(sourceName).isVirtual();
    }
    catch (Exception e) {
      System.out.println("Error: cannot determine if source is virtual: " + sourceName);
      System.out.println(e);
      // Assume it's virtual, which is safer since latestPower should work for all sources. 
      return true;
    }
  }
  
  /**
   * Returns the row number associated with this source. 
   * @param sourceName The source. 
   * @return The row this source's data is on in this spreadsheet.
   * @throws RuntimeException If we can't find this source name in the spreadsheet. 
   */
  protected int getRow(String sourceName) {
    return this.spreadsheetView.findRow(sourceName);
  }

  /**
   * Prints out a logging line if the user has supplied a different spreadsheet name in the 
   * properties file. 
   * @param spreadsheetNameKey The key to look for the user-supplied spreadsheet (if any).  
   */
  protected void logNonDefaultSpreadsheet(String spreadsheetNameKey) {
    String spreadsheetName = this.wattDepotService.getProperties().getProperty(spreadsheetNameKey);
    if (spreadsheetName != null) {
      System.out.println("Using non-default spreadsheet: " + spreadsheetName);
    }
  }
  
  /** 
   * Starts up the threads associated with this updater process.
   */
  public abstract void run();
  
  

}
